// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

/// <reference path="../../core/internal.d.ts" />

const core = globalThis.Deno.core;
const internals = globalThis.__bootstrap.internals;
const primordials = globalThis.__bootstrap.primordials;
const {
  AggregateErrorPrototype,
  ArrayPrototypeUnshift,
  isNaN,
  DatePrototype,
  DateNow,
  DatePrototypeGetTime,
  DatePrototypeToISOString,
  Boolean,
  BooleanPrototype,
  BooleanPrototypeToString,
  ObjectKeys,
  ObjectAssign,
  ObjectCreate,
  ObjectFreeze,
  ObjectIs,
  ObjectValues,
  ObjectFromEntries,
  ObjectGetPrototypeOf,
  ObjectGetOwnPropertyDescriptor,
  ObjectGetOwnPropertySymbols,
  ObjectPrototypeHasOwnProperty,
  ObjectPrototypeIsPrototypeOf,
  ObjectPrototypePropertyIsEnumerable,
  PromisePrototype,
  String,
  StringPrototype,
  StringPrototypeRepeat,
  StringPrototypeReplace,
  StringPrototypeReplaceAll,
  StringPrototypeSplit,
  StringPrototypeSlice,
  StringPrototypeCodePointAt,
  StringPrototypeCharCodeAt,
  StringPrototypeNormalize,
  StringPrototypeMatch,
  StringPrototypePadStart,
  StringPrototypeLocaleCompare,
  StringPrototypeToString,
  StringPrototypeTrim,
  StringPrototypeIncludes,
  StringPrototypeStartsWith,
  TypeError,
  NumberIsInteger,
  NumberParseInt,
  RegExpPrototype,
  RegExpPrototypeTest,
  RegExpPrototypeToString,
  SafeArrayIterator,
  SafeStringIterator,
  SafeSet,
  SafeRegExp,
  SetPrototype,
  SetPrototypeEntries,
  SetPrototypeGetSize,
  Symbol,
  SymbolPrototype,
  SymbolPrototypeToString,
  SymbolPrototypeValueOf,
  SymbolPrototypeGetDescription,
  SymbolToStringTag,
  SymbolHasInstance,
  SymbolFor,
  Array,
  ArrayIsArray,
  ArrayPrototypeJoin,
  ArrayPrototypeMap,
  ArrayPrototypeReduce,
  ArrayPrototypeEntries,
  ArrayPrototypePush,
  ArrayPrototypePop,
  ArrayPrototypeSort,
  ArrayPrototypeSlice,
  ArrayPrototypeShift,
  ArrayPrototypeIncludes,
  ArrayPrototypeFill,
  ArrayPrototypeFilter,
  ArrayPrototypeFind,
  FunctionPrototypeBind,
  FunctionPrototypeToString,
  Map,
  MapPrototype,
  MapPrototypeHas,
  MapPrototypeGet,
  MapPrototypeSet,
  MapPrototypeDelete,
  MapPrototypeEntries,
  MapPrototypeForEach,
  MapPrototypeGetSize,
  Error,
  ErrorPrototype,
  ErrorCaptureStackTrace,
  MathAbs,
  MathMax,
  MathMin,
  MathSqrt,
  MathRound,
  MathFloor,
  Number,
  NumberPrototype,
  NumberPrototypeToString,
  NumberPrototypeValueOf,
  BigIntPrototype,
  BigIntPrototypeToString,
  Proxy,
  ReflectGet,
  ReflectGetOwnPropertyDescriptor,
  ReflectGetPrototypeOf,
  ReflectHas,
  TypedArrayPrototypeGetLength,
  TypedArrayPrototypeGetSymbolToStringTag,
  WeakMapPrototype,
  WeakSetPrototype,
} = primordials;
import * as colors from "ext:deno_console/01_colors.js";

function isInvalidDate(x) {
  return isNaN(DatePrototypeGetTime(x));
}

function hasOwnProperty(obj, v) {
  if (obj == null) {
    return false;
  }
  return ObjectPrototypeHasOwnProperty(obj, v);
}

function propertyIsEnumerable(obj, prop) {
  if (
    obj == null ||
    typeof obj.propertyIsEnumerable !== "function"
  ) {
    return false;
  }

  return ObjectPrototypePropertyIsEnumerable(obj, prop);
}

// Copyright Joyent, Inc. and other Node contributors. MIT license.
// Forked from Node's lib/internal/cli_table.js

function isTypedArray(x) {
  return TypedArrayPrototypeGetSymbolToStringTag(x) !== undefined;
}

const tableChars = {
  middleMiddle: "\u2500",
  rowMiddle: "\u253c",
  topRight: "\u2510",
  topLeft: "\u250c",
  leftMiddle: "\u251c",
  topMiddle: "\u252c",
  bottomRight: "\u2518",
  bottomLeft: "\u2514",
  bottomMiddle: "\u2534",
  rightMiddle: "\u2524",
  left: "\u2502 ",
  right: " \u2502",
  middle: " \u2502 ",
};

function isFullWidthCodePoint(code) {
  // Code points are partially derived from:
  // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
  return (
    code >= 0x1100 &&
    (code <= 0x115f || // Hangul Jamo
      code === 0x2329 || // LEFT-POINTING ANGLE BRACKET
      code === 0x232a || // RIGHT-POINTING ANGLE BRACKET
      // CJK Radicals Supplement .. Enclosed CJK Letters and Months
      (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) ||
      // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
      (code >= 0x3250 && code <= 0x4dbf) ||
      // CJK Unified Ideographs .. Yi Radicals
      (code >= 0x4e00 && code <= 0xa4c6) ||
      // Hangul Jamo Extended-A
      (code >= 0xa960 && code <= 0xa97c) ||
      // Hangul Syllables
      (code >= 0xac00 && code <= 0xd7a3) ||
      // CJK Compatibility Ideographs
      (code >= 0xf900 && code <= 0xfaff) ||
      // Vertical Forms
      (code >= 0xfe10 && code <= 0xfe19) ||
      // CJK Compatibility Forms .. Small Form Variants
      (code >= 0xfe30 && code <= 0xfe6b) ||
      // Halfwidth and Fullwidth Forms
      (code >= 0xff01 && code <= 0xff60) ||
      (code >= 0xffe0 && code <= 0xffe6) ||
      // Kana Supplement
      (code >= 0x1b000 && code <= 0x1b001) ||
      // Enclosed Ideographic Supplement
      (code >= 0x1f200 && code <= 0x1f251) ||
      // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff
      // Emoticons 0x1f600 - 0x1f64f
      (code >= 0x1f300 && code <= 0x1f64f) ||
      // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
      (code >= 0x20000 && code <= 0x3fffd))
  );
}

function getStringWidth(str) {
  str = StringPrototypeNormalize(colors.stripColor(str), "NFC");
  let width = 0;

  for (const ch of new SafeStringIterator(str)) {
    width += isFullWidthCodePoint(StringPrototypeCodePointAt(ch, 0)) ? 2 : 1;
  }

  return width;
}

function renderRow(row, columnWidths, columnRightAlign) {
  let out = tableChars.left;
  for (let i = 0; i < row.length; i++) {
    const cell = row[i];
    const len = getStringWidth(cell);
    const padding = StringPrototypeRepeat(" ", columnWidths[i] - len);
    if (columnRightAlign?.[i]) {
      out += `${padding}${cell}`;
    } else {
      out += `${cell}${padding}`;
    }
    if (i !== row.length - 1) {
      out += tableChars.middle;
    }
  }
  out += tableChars.right;
  return out;
}

function cliTable(head, columns) {
  const rows = [];
  const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h));
  const longestColumn = ArrayPrototypeReduce(
    columns,
    (n, a) => MathMax(n, a.length),
    0,
  );
  const columnRightAlign = new Array(columnWidths.length).fill(true);

  for (let i = 0; i < head.length; i++) {
    const column = columns[i];
    for (let j = 0; j < longestColumn; j++) {
      if (rows[j] === undefined) {
        rows[j] = [];
      }
      const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : "");
      const width = columnWidths[i] || 0;
      const counted = getStringWidth(value);
      columnWidths[i] = MathMax(width, counted);
      columnRightAlign[i] &= NumberIsInteger(+value);
    }
  }

  const divider = ArrayPrototypeMap(
    columnWidths,
    (i) => StringPrototypeRepeat(tableChars.middleMiddle, i + 2),
  );

  let result =
    `${tableChars.topLeft}${
      ArrayPrototypeJoin(divider, tableChars.topMiddle)
    }` +
    `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` +
    `${tableChars.leftMiddle}${
      ArrayPrototypeJoin(divider, tableChars.rowMiddle)
    }` +
    `${tableChars.rightMiddle}\n`;

  for (let i = 0; i < rows.length; ++i) {
    const row = rows[i];
    result += `${renderRow(row, columnWidths, columnRightAlign)}\n`;
  }

  result +=
    `${tableChars.bottomLeft}${
      ArrayPrototypeJoin(divider, tableChars.bottomMiddle)
    }` +
    tableChars.bottomRight;

  return result;
}
/* End of forked part */

// We can match Node's quoting behavior exactly by swapping the double quote and
// single quote in this array. That would give preference to single quotes.
// However, we prefer double quotes as the default.
const QUOTES = ['"', "'", "`"];

const DEFAULT_INSPECT_OPTIONS = {
  depth: 4,
  indentLevel: 0,
  sorted: false,
  trailingComma: false,
  compact: true,
  iterableLimit: 100,
  showProxy: false,
  colors: false,
  getters: false,
  showHidden: false,
  strAbbreviateSize: 100,
  /** You can override the quotes preference in inspectString.
   * Used by util.inspect() */
  // TODO(kt3k): Consider using symbol as a key to hide this from the public
  // API.
  quotes: QUOTES,
};

const DEFAULT_INDENT = "  "; // Default indent string

const LINE_BREAKING_LENGTH = 80;
const MIN_GROUP_LENGTH = 6;
const STR_ABBREVIATE_SIZE = 100;

const PROMISE_STRING_BASE_LENGTH = 12;

class CSI {
  static kClear = "\x1b[1;1H";
  static kClearScreenDown = "\x1b[0J";
}

function getClassInstanceName(instance) {
  if (typeof instance != "object") {
    return "";
  }
  const constructor = instance?.constructor;
  if (typeof constructor == "function") {
    return constructor.name ?? "";
  }
  return "";
}

function maybeColor(fn, inspectOptions) {
  return inspectOptions.colors ? fn : (s) => s;
}

function inspectFunction(value, inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  if (
    ReflectHas(value, customInspect) &&
    typeof value[customInspect] === "function"
  ) {
    return String(value[customInspect](inspect, inspectOptions));
  }
  // Might be Function/AsyncFunction/GeneratorFunction/AsyncGeneratorFunction
  let cstrName = ObjectGetPrototypeOf(value)?.constructor?.name;
  if (!cstrName) {
    // If prototype is removed or broken,
    // use generic 'Function' instead.
    cstrName = "Function";
  }
  const stringValue = FunctionPrototypeToString(value);
  // Might be Class
  if (StringPrototypeStartsWith(stringValue, "class")) {
    cstrName = "Class";
  }

  // Our function may have properties, so we want to format those
  // as if our function was an object
  // If we didn't find any properties, we will just append an
  // empty suffix.
  let suffix = ``;
  let refStr = "";
  if (
    ObjectKeys(value).length > 0 ||
    ObjectGetOwnPropertySymbols(value).length > 0
  ) {
    const { 0: propString, 1: refIndex } = inspectRawObject(
      value,
      inspectOptions,
    );
    refStr = refIndex;
    // Filter out the empty string for the case we only have
    // non-enumerable symbols.
    if (
      propString.length > 0 &&
      propString !== "{}"
    ) {
      suffix = ` ${propString}`;
    }
  }

  if (value.name && value.name !== "anonymous") {
    // from MDN spec
    return cyan(`${refStr}[${cstrName}: ${value.name}]`) + suffix;
  }
  return cyan(`${refStr}[${cstrName} (anonymous)]`) + suffix;
}

function inspectIterable(
  value,
  options,
  inspectOptions,
) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  if (inspectOptions.indentLevel >= inspectOptions.depth) {
    return cyan(`[${options.typeName}]`);
  }

  const entries = [];
  let iter;
  let valueIsTypedArray = false;
  let entriesLength;

  switch (options.typeName) {
    case "Map":
      iter = MapPrototypeEntries(value);
      entriesLength = MapPrototypeGetSize(value);
      break;
    case "Set":
      iter = SetPrototypeEntries(value);
      entriesLength = SetPrototypeGetSize(value);
      break;
    case "Array":
      entriesLength = value.length;
      break;
    default:
      if (isTypedArray(value)) {
        entriesLength = TypedArrayPrototypeGetLength(value);
        iter = ArrayPrototypeEntries(value);
        valueIsTypedArray = true;
      } else {
        throw new TypeError("unreachable");
      }
  }

  let entriesLengthWithoutEmptyItems = entriesLength;
  if (options.typeName === "Array") {
    for (
      let i = 0, j = 0;
      i < entriesLength && j < inspectOptions.iterableLimit;
      i++, j++
    ) {
      inspectOptions.indentLevel++;
      const { entry, skipTo } = options.entryHandler(
        [i, value[i]],
        inspectOptions,
      );
      ArrayPrototypePush(entries, entry);
      inspectOptions.indentLevel--;

      if (skipTo) {
        // subtract skipped (empty) items
        entriesLengthWithoutEmptyItems -= skipTo - i;
        i = skipTo;
      }
    }
  } else {
    let i = 0;
    while (true) {
      let el;
      try {
        const res = iter.next();
        if (res.done) {
          break;
        }
        el = res.value;
      } catch (err) {
        if (valueIsTypedArray) {
          // TypedArray.prototype.entries doesn't throw, unless the ArrayBuffer
          // is detached. We don't want to show the exception in that case, so
          // we catch it here and pretend the ArrayBuffer has no entries (like
          // Chrome DevTools does).
          break;
        }
        throw err;
      }
      if (i < inspectOptions.iterableLimit) {
        inspectOptions.indentLevel++;
        ArrayPrototypePush(
          entries,
          options.entryHandler(
            el,
            inspectOptions,
          ),
        );
        inspectOptions.indentLevel--;
      } else {
        break;
      }
      i++;
    }
  }

  if (options.sort) {
    ArrayPrototypeSort(entries);
  }

  if (entriesLengthWithoutEmptyItems > inspectOptions.iterableLimit) {
    const nmore = entriesLengthWithoutEmptyItems -
      inspectOptions.iterableLimit;
    ArrayPrototypePush(entries, `... ${nmore} more items`);
  }

  const iPrefix = `${options.displayName ? options.displayName + " " : ""}`;

  const level = inspectOptions.indentLevel;
  const initIndentation = `\n${
    StringPrototypeRepeat(DEFAULT_INDENT, level + 1)
  }`;
  const entryIndentation = `,\n${
    StringPrototypeRepeat(DEFAULT_INDENT, level + 1)
  }`;
  const closingDelimIndentation = StringPrototypeRepeat(
    DEFAULT_INDENT,
    level,
  );
  const closingIndentation = `${
    inspectOptions.trailingComma ? "," : ""
  }\n${closingDelimIndentation}`;

  let iContent;
  if (entries.length === 0 && !inspectOptions.compact) {
    iContent = `\n${closingDelimIndentation}`;
  } else if (options.group && entries.length > MIN_GROUP_LENGTH) {
    const groups = groupEntries(entries, level, value);
    iContent = `${initIndentation}${
      ArrayPrototypeJoin(groups, entryIndentation)
    }${closingIndentation}`;
  } else {
    iContent = entries.length === 0
      ? ""
      : ` ${ArrayPrototypeJoin(entries, ", ")} `;
    if (
      colors.stripColor(iContent).length > LINE_BREAKING_LENGTH ||
      !inspectOptions.compact
    ) {
      iContent = `${initIndentation}${
        ArrayPrototypeJoin(entries, entryIndentation)
      }${closingIndentation}`;
    }
  }

  return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`;
}

// Ported from Node.js
// Copyright Node.js contributors. All rights reserved.
function groupEntries(
  entries,
  level,
  value,
  iterableLimit = 100,
) {
  let totalLength = 0;
  let maxLength = 0;
  let entriesLength = entries.length;
  if (iterableLimit < entriesLength) {
    // This makes sure the "... n more items" part is not taken into account.
    entriesLength--;
  }
  const separatorSpace = 2; // Add 1 for the space and 1 for the separator.
  const dataLen = new Array(entriesLength);
  // Calculate the total length of all output entries and the individual max
  // entries length of all output entries.
  // IN PROGRESS: Colors are being taken into account.
  for (let i = 0; i < entriesLength; i++) {
    // Taking colors into account: removing the ANSI color
    // codes from the string before measuring its length
    const len = colors.stripColor(entries[i]).length;
    dataLen[i] = len;
    totalLength += len + separatorSpace;
    if (maxLength < len) maxLength = len;
  }
  // Add two to `maxLength` as we add a single whitespace character plus a comma
  // in-between two entries.
  const actualMax = maxLength + separatorSpace;
  // Check if at least three entries fit next to each other and prevent grouping
  // of arrays that contains entries of very different length (i.e., if a single
  // entry is longer than 1/5 of all other entries combined). Otherwise the
  // space in-between small entries would be enormous.
  if (
    actualMax * 3 + (level + 1) < LINE_BREAKING_LENGTH &&
    (totalLength / actualMax > 5 || maxLength <= 6)
  ) {
    const approxCharHeights = 2.5;
    const averageBias = MathSqrt(actualMax - totalLength / entries.length);
    const biasedMax = MathMax(actualMax - 3 - averageBias, 1);
    // Dynamically check how many columns seem possible.
    const columns = MathMin(
      // Ideally a square should be drawn. We expect a character to be about 2.5
      // times as high as wide. This is the area formula to calculate a square
      // which contains n rectangles of size `actualMax * approxCharHeights`.
      // Divide that by `actualMax` to receive the correct number of columns.
      // The added bias increases the columns for short entries.
      MathRound(
        MathSqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax,
      ),
      // Do not exceed the breakLength.
      MathFloor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax),
      // Limit the columns to a maximum of fifteen.
      15,
    );
    // Return with the original output if no grouping should happen.
    if (columns <= 1) {
      return entries;
    }
    const tmp = [];
    const maxLineLength = [];
    for (let i = 0; i < columns; i++) {
      let lineMaxLength = 0;
      for (let j = i; j < entries.length; j += columns) {
        if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j];
      }
      lineMaxLength += separatorSpace;
      maxLineLength[i] = lineMaxLength;
    }
    let order = "padStart";
    if (value !== undefined) {
      for (let i = 0; i < entries.length; i++) {
        if (
          typeof value[i] !== "number" &&
          typeof value[i] !== "bigint"
        ) {
          order = "padEnd";
          break;
        }
      }
    }
    // Each iteration creates a single line of grouped entries.
    for (let i = 0; i < entriesLength; i += columns) {
      // The last lines may contain less entries than columns.
      const max = MathMin(i + columns, entriesLength);
      let str = "";
      let j = i;
      for (; j < max - 1; j++) {
        const lengthOfColorCodes = entries[j].length - dataLen[j];
        const padding = maxLineLength[j - i] + lengthOfColorCodes;
        str += `${entries[j]}, `[order](padding, " ");
      }
      if (order === "padStart") {
        const lengthOfColorCodes = entries[j].length - dataLen[j];
        const padding = maxLineLength[j - i] +
          lengthOfColorCodes -
          separatorSpace;
        str += StringPrototypePadStart(entries[j], padding, " ");
      } else {
        str += entries[j];
      }
      ArrayPrototypePush(tmp, str);
    }
    if (iterableLimit < entries.length) {
      ArrayPrototypePush(tmp, entries[entriesLength]);
    }
    entries = tmp;
  }
  return entries;
}

let circular;
function handleCircular(value, cyan) {
  let index = 1;
  if (circular === undefined) {
    circular = new Map();
    MapPrototypeSet(circular, value, index);
  } else {
    index = MapPrototypeGet(circular, value);
    if (index === undefined) {
      index = MapPrototypeGetSize(circular) + 1;
      MapPrototypeSet(circular, value, index);
    }
  }
  // Circular string is cyan
  return cyan(`[Circular *${index}]`);
}

function _inspectValue(
  value,
  inspectOptions,
) {
  const proxyDetails = core.getProxyDetails(value);
  if (proxyDetails != null && inspectOptions.showProxy) {
    return inspectProxy(proxyDetails, inspectOptions);
  }

  const green = maybeColor(colors.green, inspectOptions);
  const yellow = maybeColor(colors.yellow, inspectOptions);
  const gray = maybeColor(colors.gray, inspectOptions);
  const cyan = maybeColor(colors.cyan, inspectOptions);
  const bold = maybeColor(colors.bold, inspectOptions);
  const red = maybeColor(colors.red, inspectOptions);

  switch (typeof value) {
    case "string":
      return green(quoteString(value, inspectOptions));
    case "number": // Numbers are yellow
      // Special handling of -0
      return yellow(ObjectIs(value, -0) ? "-0" : `${value}`);
    case "boolean": // booleans are yellow
      return yellow(String(value));
    case "undefined": // undefined is gray
      return gray(String(value));
    case "symbol": // Symbols are green
      return green(maybeQuoteSymbol(value, inspectOptions));
    case "bigint": // Bigints are yellow
      return yellow(`${value}n`);
    case "function": // Function string is cyan
      if (ctxHas(value)) {
        // Circular string is cyan
        return handleCircular(value, cyan);
      }

      return inspectFunction(value, inspectOptions);
    case "object": // null is bold
      if (value === null) {
        return bold("null");
      }

      if (ctxHas(value)) {
        return handleCircular(value, cyan);
      }

      return inspectObject(
        value,
        inspectOptions,
        proxyDetails,
      );
    default:
      // Not implemented is red
      return red("[Not Implemented]");
  }
}

function inspectValue(
  value,
  inspectOptions,
) {
  ArrayPrototypePush(CTX_STACK, value);
  let x;
  try {
    x = _inspectValue(value, inspectOptions);
  } finally {
    ArrayPrototypePop(CTX_STACK);
  }
  return x;
}

/** Surround the string in quotes.
 *
 * The quote symbol is chosen by taking the first of the `QUOTES` array which
 * does not occur in the string. If they all occur, settle with `QUOTES[0]`.
 *
 * Insert a backslash before any occurrence of the chosen quote symbol and
 * before any backslash.
 */
function quoteString(string, inspectOptions = DEFAULT_INSPECT_OPTIONS) {
  const quotes = inspectOptions.quotes;
  const quote =
    ArrayPrototypeFind(quotes, (c) => !StringPrototypeIncludes(string, c)) ??
      quotes[0];
  const escapePattern = new SafeRegExp(`(?=[${quote}\\\\])`, "g");
  string = StringPrototypeReplace(string, escapePattern, "\\");
  string = replaceEscapeSequences(string);
  return `${quote}${string}${quote}`;
}

const ESCAPE_PATTERN = new SafeRegExp(/([\b\f\n\r\t\v])/g);
const ESCAPE_MAP = ObjectFreeze({
  "\b": "\\b",
  "\f": "\\f",
  "\n": "\\n",
  "\r": "\\r",
  "\t": "\\t",
  "\v": "\\v",
});

// deno-lint-ignore no-control-regex
const ESCAPE_PATTERN2 = new SafeRegExp(/[\x00-\x1f\x7f-\x9f]/g);

// Replace escape sequences that can modify output.
function replaceEscapeSequences(string) {
  return StringPrototypeReplace(
    StringPrototypeReplace(
      string,
      ESCAPE_PATTERN,
      (c) => ESCAPE_MAP[c],
    ),
    new SafeRegExp(ESCAPE_PATTERN2),
    (c) =>
      "\\x" +
      StringPrototypePadStart(
        NumberPrototypeToString(StringPrototypeCharCodeAt(c, 0), 16),
        2,
        "0",
      ),
  );
}

const QUOTE_STRING_PATTERN = new SafeRegExp(/^[a-zA-Z_][a-zA-Z_0-9]*$/);

// Surround a string with quotes when it is required (e.g the string not a valid identifier).
function maybeQuoteString(string, inspectOptions) {
  if (
    RegExpPrototypeTest(QUOTE_STRING_PATTERN, string)
  ) {
    return replaceEscapeSequences(string);
  }

  return quoteString(string, inspectOptions);
}

const QUOTE_SYMBOL_REG = new SafeRegExp(/^[a-zA-Z_][a-zA-Z_.0-9]*$/);

// Surround a symbol's description in quotes when it is required (e.g the description has non printable characters).
function maybeQuoteSymbol(symbol, inspectOptions) {
  const description = SymbolPrototypeGetDescription(symbol);

  if (description === undefined) {
    return SymbolPrototypeToString(symbol);
  }

  if (RegExpPrototypeTest(QUOTE_SYMBOL_REG, description)) {
    return SymbolPrototypeToString(symbol);
  }

  return `Symbol(${quoteString(description, inspectOptions)})`;
}

const CTX_STACK = [];
function ctxHas(x) {
  // Only check parent contexts
  return ArrayPrototypeIncludes(
    ArrayPrototypeSlice(CTX_STACK, 0, CTX_STACK.length - 1),
    x,
  );
}

// Print strings when they are inside of arrays or objects with quotes
function inspectValueWithQuotes(
  value,
  inspectOptions,
) {
  const abbreviateSize = typeof inspectOptions.strAbbreviateSize === "undefined"
    ? STR_ABBREVIATE_SIZE
    : inspectOptions.strAbbreviateSize;
  const green = maybeColor(colors.green, inspectOptions);
  switch (typeof value) {
    case "string": {
      const trunc = value.length > abbreviateSize
        ? StringPrototypeSlice(value, 0, abbreviateSize) + "..."
        : value;
      return green(quoteString(trunc, inspectOptions)); // Quoted strings are green
    }
    default:
      return inspectValue(value, inspectOptions);
  }
}

function inspectArray(
  value,
  inspectOptions,
) {
  const gray = maybeColor(colors.gray, inspectOptions);
  let lastValidIndex = 0;
  let keys;
  const options = {
    typeName: "Array",
    displayName: "",
    delims: ["[", "]"],
    entryHandler: (entry, inspectOptions) => {
      const { 0: index, 1: val } = entry;
      let i = index;
      lastValidIndex = index;
      if (!ObjectPrototypeHasOwnProperty(value, i)) {
        let skipTo;
        keys = keys || ObjectKeys(value);
        i = value.length;
        if (keys.length === 0) {
          // fast path, all items are empty
          skipTo = i;
        } else {
          // Not all indexes are empty or there's a non-index property
          // Find first non-empty array index
          while (keys.length) {
            const key = ArrayPrototypeShift(keys);
            // check if it's a valid array index
            if (key > lastValidIndex && key < 2 ** 32 - 1) {
              i = Number(key);
              break;
            }
          }

          skipTo = i - 1;
        }
        const emptyItems = i - index;
        const ending = emptyItems > 1 ? "s" : "";
        return {
          entry: gray(`<${emptyItems} empty item${ending}>`),
          skipTo,
        };
      } else {
        return { entry: inspectValueWithQuotes(val, inspectOptions) };
      }
    },
    group: inspectOptions.compact,
    sort: false,
  };
  return inspectIterable(value, options, inspectOptions);
}

function inspectTypedArray(
  typedArrayName,
  value,
  inspectOptions,
) {
  const valueLength = value.length;
  const options = {
    typeName: typedArrayName,
    displayName: `${typedArrayName}(${valueLength})`,
    delims: ["[", "]"],
    entryHandler: (entry, inspectOptions) => {
      const val = entry[1];
      inspectOptions.indentLevel++;
      const inspectedValue = inspectValueWithQuotes(val, inspectOptions);
      inspectOptions.indentLevel--;
      return inspectedValue;
    },
    group: inspectOptions.compact,
    sort: false,
  };
  return inspectIterable(value, options, inspectOptions);
}

function inspectSet(
  value,
  inspectOptions,
) {
  const options = {
    typeName: "Set",
    displayName: "Set",
    delims: ["{", "}"],
    entryHandler: (entry, inspectOptions) => {
      const val = entry[1];
      inspectOptions.indentLevel++;
      const inspectedValue = inspectValueWithQuotes(val, inspectOptions);
      inspectOptions.indentLevel--;
      return inspectedValue;
    },
    group: false,
    sort: inspectOptions.sorted,
  };
  return inspectIterable(value, options, inspectOptions);
}

function inspectMap(
  value,
  inspectOptions,
) {
  const options = {
    typeName: "Map",
    displayName: "Map",
    delims: ["{", "}"],
    entryHandler: (entry, inspectOptions) => {
      const { 0: key, 1: val } = entry;
      inspectOptions.indentLevel++;
      const inspectedValue = `${
        inspectValueWithQuotes(key, inspectOptions)
      } => ${inspectValueWithQuotes(val, inspectOptions)}`;
      inspectOptions.indentLevel--;
      return inspectedValue;
    },
    group: false,
    sort: inspectOptions.sorted,
  };
  return inspectIterable(
    value,
    options,
    inspectOptions,
  );
}

function inspectWeakSet(inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
}

function inspectWeakMap(inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
}

function inspectDate(value, inspectOptions) {
  // without quotes, ISO format, in magenta like before
  const magenta = maybeColor(colors.magenta, inspectOptions);
  return magenta(
    isInvalidDate(value) ? "Invalid Date" : DatePrototypeToISOString(value),
  );
}

function inspectRegExp(value, inspectOptions) {
  const red = maybeColor(colors.red, inspectOptions);
  return red(RegExpPrototypeToString(value)); // RegExps are red
}

const AGGREGATE_ERROR_HAS_AT_PATTERN = new SafeRegExp(/\s+at/);
const AGGREGATE_ERROR_NOT_EMPTY_LINE_PATTERN = new SafeRegExp(/^(?!\s*$)/gm);

function inspectError(value, cyan) {
  const causes = [value];

  let err = value;
  while (err.cause) {
    if (ArrayPrototypeIncludes(causes, err.cause)) {
      ArrayPrototypePush(causes, handleCircular(err.cause, cyan));
      break;
    } else {
      ArrayPrototypePush(causes, err.cause);
      err = err.cause;
    }
  }

  const refMap = new Map();
  for (let i = 0; i < causes.length; ++i) {
    const cause = causes[i];
    if (circular !== undefined) {
      const index = MapPrototypeGet(circular, cause);
      if (index !== undefined) {
        MapPrototypeSet(refMap, cause, cyan(`<ref *${index}> `));
      }
    }
  }
  ArrayPrototypeShift(causes);

  let finalMessage = MapPrototypeGet(refMap, value) ?? "";

  if (ObjectPrototypeIsPrototypeOf(AggregateErrorPrototype, value)) {
    const stackLines = StringPrototypeSplit(value.stack, "\n");
    while (true) {
      const line = ArrayPrototypeShift(stackLines);
      if (RegExpPrototypeTest(AGGREGATE_ERROR_HAS_AT_PATTERN, line)) {
        ArrayPrototypeUnshift(stackLines, line);
        break;
      } else if (typeof line === "undefined") {
        break;
      }

      finalMessage += line;
      finalMessage += "\n";
    }
    const aggregateMessage = ArrayPrototypeJoin(
      ArrayPrototypeMap(
        value.errors,
        (error) =>
          StringPrototypeReplace(
            inspectArgs([error]),
            AGGREGATE_ERROR_NOT_EMPTY_LINE_PATTERN,
            StringPrototypeRepeat(" ", 4),
          ),
      ),
      "\n",
    );
    finalMessage += aggregateMessage;
    finalMessage += "\n";
    finalMessage += ArrayPrototypeJoin(stackLines, "\n");
  } else {
    finalMessage += value.stack;
  }

  finalMessage += ArrayPrototypeJoin(
    ArrayPrototypeMap(
      causes,
      (cause) =>
        "\nCaused by " + (MapPrototypeGet(refMap, cause) ?? "") +
        (cause?.stack ?? cause),
    ),
    "",
  );

  return finalMessage;
}

function inspectStringObject(value, inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  return cyan(`[String: "${StringPrototypeToString(value)}"]`); // wrappers are in cyan
}

function inspectBooleanObject(value, inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  return cyan(`[Boolean: ${BooleanPrototypeToString(value)}]`); // wrappers are in cyan
}

function inspectNumberObject(value, inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  // Special handling of -0
  return cyan(
    `[Number: ${
      ObjectIs(NumberPrototypeValueOf(value), -0)
        ? "-0"
        : NumberPrototypeToString(value)
    }]`,
  ); // wrappers are in cyan
}

function inspectBigIntObject(value, inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  return cyan(`[BigInt: ${BigIntPrototypeToString(value)}n]`); // wrappers are in cyan
}

function inspectSymbolObject(value, inspectOptions) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  return cyan(
    `[Symbol: ${
      maybeQuoteSymbol(SymbolPrototypeValueOf(value), inspectOptions)
    }]`,
  ); // wrappers are in cyan
}

const PromiseState = {
  Pending: 0,
  Fulfilled: 1,
  Rejected: 2,
};

function inspectPromise(
  value,
  inspectOptions,
) {
  const cyan = maybeColor(colors.cyan, inspectOptions);
  const red = maybeColor(colors.red, inspectOptions);

  const { 0: state, 1: result } = core.getPromiseDetails(value);

  if (state === PromiseState.Pending) {
    return `Promise { ${cyan("<pending>")} }`;
  }

  const prefix = state === PromiseState.Fulfilled
    ? ""
    : `${red("<rejected>")} `;

  inspectOptions.indentLevel++;
  const str = `${prefix}${inspectValueWithQuotes(result, inspectOptions)}`;
  inspectOptions.indentLevel--;

  if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
    return `Promise {\n${
      StringPrototypeRepeat(DEFAULT_INDENT, inspectOptions.indentLevel + 1)
    }${str}\n}`;
  }

  return `Promise { ${str} }`;
}

function inspectProxy(
  targetAndHandler,
  inspectOptions,
) {
  return `Proxy ${inspectArray(targetAndHandler, inspectOptions)}`;
}

function inspectRawObject(
  value,
  inspectOptions,
) {
  const cyan = maybeColor(colors.cyan, inspectOptions);

  if (inspectOptions.indentLevel >= inspectOptions.depth) {
    return [cyan("[Object]"), ""]; // wrappers are in cyan
  }

  let baseString;

  let shouldShowDisplayName = false;
  let displayName = value[
    SymbolToStringTag
  ];
  if (!displayName) {
    displayName = getClassInstanceName(value);
  }
  if (
    displayName && displayName !== "Object" && displayName !== "anonymous"
  ) {
    shouldShowDisplayName = true;
  }

  const entries = [];
  const stringKeys = ObjectKeys(value);
  const symbolKeys = ObjectGetOwnPropertySymbols(value);
  if (inspectOptions.sorted) {
    ArrayPrototypeSort(stringKeys);
    ArrayPrototypeSort(
      symbolKeys,
      (s1, s2) =>
        StringPrototypeLocaleCompare(
          SymbolPrototypeGetDescription(s1) ?? "",
          SymbolPrototypeGetDescription(s2) ?? "",
        ),
    );
  }

  const red = maybeColor(colors.red, inspectOptions);

  inspectOptions.indentLevel++;

  for (let i = 0; i < stringKeys.length; ++i) {
    const key = stringKeys[i];
    if (inspectOptions.getters) {
      let propertyValue;
      let error = null;
      try {
        propertyValue = value[key];
      } catch (error_) {
        error = error_;
      }
      const inspectedValue = error == null
        ? inspectValueWithQuotes(propertyValue, inspectOptions)
        : red(`[Thrown ${error.name}: ${error.message}]`);
      ArrayPrototypePush(
        entries,
        `${maybeQuoteString(key, inspectOptions)}: ${inspectedValue}`,
      );
    } else {
      const descriptor = ObjectGetOwnPropertyDescriptor(value, key);
      if (descriptor.get !== undefined && descriptor.set !== undefined) {
        ArrayPrototypePush(
          entries,
          `${maybeQuoteString(key, inspectOptions)}: [Getter/Setter]`,
        );
      } else if (descriptor.get !== undefined) {
        ArrayPrototypePush(
          entries,
          `${maybeQuoteString(key, inspectOptions)}: [Getter]`,
        );
      } else {
        ArrayPrototypePush(
          entries,
          `${maybeQuoteString(key, inspectOptions)}: ${
            inspectValueWithQuotes(value[key], inspectOptions)
          }`,
        );
      }
    }
  }

  for (let i = 0; i < symbolKeys.length; ++i) {
    const key = symbolKeys[i];
    if (
      !inspectOptions.showHidden &&
      !propertyIsEnumerable(value, key)
    ) {
      continue;
    }

    if (inspectOptions.getters) {
      let propertyValue;
      let error;
      try {
        propertyValue = value[key];
      } catch (error_) {
        error = error_;
      }
      const inspectedValue = error == null
        ? inspectValueWithQuotes(propertyValue, inspectOptions)
        : red(`Thrown ${error.name}: ${error.message}`);
      ArrayPrototypePush(
        entries,
        `[${maybeQuoteSymbol(key, inspectOptions)}]: ${inspectedValue}`,
      );
    } else {
      const descriptor = ObjectGetOwnPropertyDescriptor(value, key);
      if (descriptor.get !== undefined && descriptor.set !== undefined) {
        ArrayPrototypePush(
          entries,
          `[${maybeQuoteSymbol(key, inspectOptions)}]: [Getter/Setter]`,
        );
      } else if (descriptor.get !== undefined) {
        ArrayPrototypePush(
          entries,
          `[${maybeQuoteSymbol(key, inspectOptions)}]: [Getter]`,
        );
      } else {
        ArrayPrototypePush(
          entries,
          `[${maybeQuoteSymbol(key, inspectOptions)}]: ${
            inspectValueWithQuotes(value[key], inspectOptions)
          }`,
        );
      }
    }
  }

  inspectOptions.indentLevel--;

  // Making sure color codes are ignored when calculating the total length
  const entriesText = colors.stripColor(ArrayPrototypeJoin(entries, ""));
  const totalLength = entries.length + inspectOptions.indentLevel +
    entriesText.length;

  if (entries.length === 0) {
    baseString = "{}";
  } else if (
    totalLength > LINE_BREAKING_LENGTH ||
    !inspectOptions.compact ||
    StringPrototypeIncludes(entriesText, "\n")
  ) {
    const entryIndent = StringPrototypeRepeat(
      DEFAULT_INDENT,
      inspectOptions.indentLevel + 1,
    );
    const closingIndent = StringPrototypeRepeat(
      DEFAULT_INDENT,
      inspectOptions.indentLevel,
    );
    baseString = `{\n${entryIndent}${
      ArrayPrototypeJoin(entries, `,\n${entryIndent}`)
    }${inspectOptions.trailingComma ? "," : ""}\n${closingIndent}}`;
  } else {
    baseString = `{ ${ArrayPrototypeJoin(entries, ", ")} }`;
  }

  if (shouldShowDisplayName) {
    baseString = `${displayName} ${baseString}`;
  }

  let refIndex = "";
  if (circular !== undefined) {
    const index = MapPrototypeGet(circular, value);
    if (index !== undefined) {
      refIndex = cyan(`<ref *${index}> `);
    }
  }

  return [baseString, refIndex];
}

function inspectObject(value, inspectOptions, proxyDetails) {
  if (
    ReflectHas(value, customInspect) &&
    typeof value[customInspect] === "function"
  ) {
    return String(value[customInspect](inspect, inspectOptions));
  }
  // This non-unique symbol is used to support op_crates, ie.
  // in extensions/web we don't want to depend on public
  // Symbol.for("Deno.customInspect") symbol defined in the public API.
  // Internal only, shouldn't be used by users.
  const privateCustomInspect = SymbolFor("Deno.privateCustomInspect");
  if (
    ReflectHas(value, privateCustomInspect) &&
    typeof value[privateCustomInspect] === "function"
  ) {
    // TODO(nayeemrmn): `inspect` is passed as an argument because custom
    // inspect implementations in `extensions` need it, but may not have access
    // to the `Deno` namespace in web workers. Remove when the `Deno`
    // namespace is always enabled.
    return String(
      value[privateCustomInspect](inspect, inspectOptions),
    );
  }
  if (ObjectPrototypeIsPrototypeOf(ErrorPrototype, value)) {
    return inspectError(value, maybeColor(colors.cyan, inspectOptions));
  } else if (ArrayIsArray(value)) {
    return inspectArray(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(NumberPrototype, value)) {
    return inspectNumberObject(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(BigIntPrototype, value)) {
    return inspectBigIntObject(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(BooleanPrototype, value)) {
    return inspectBooleanObject(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(StringPrototype, value)) {
    return inspectStringObject(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(SymbolPrototype, value)) {
    return inspectSymbolObject(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(PromisePrototype, value)) {
    return inspectPromise(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(RegExpPrototype, value)) {
    return inspectRegExp(value, inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(DatePrototype, value)) {
    return inspectDate(
      proxyDetails ? proxyDetails[0] : value,
      inspectOptions,
    );
  } else if (ObjectPrototypeIsPrototypeOf(SetPrototype, value)) {
    return inspectSet(
      proxyDetails ? proxyDetails[0] : value,
      inspectOptions,
    );
  } else if (ObjectPrototypeIsPrototypeOf(MapPrototype, value)) {
    return inspectMap(
      proxyDetails ? proxyDetails[0] : value,
      inspectOptions,
    );
  } else if (ObjectPrototypeIsPrototypeOf(WeakSetPrototype, value)) {
    return inspectWeakSet(inspectOptions);
  } else if (ObjectPrototypeIsPrototypeOf(WeakMapPrototype, value)) {
    return inspectWeakMap(inspectOptions);
  } else if (isTypedArray(value)) {
    return inspectTypedArray(
      ObjectGetPrototypeOf(value).constructor.name,
      value,
      inspectOptions,
    );
  } else {
    // Otherwise, default object formatting
    let { 0: insp, 1: refIndex } = inspectRawObject(value, inspectOptions);
    insp = refIndex + insp;
    return insp;
  }
}

const colorKeywords = new Map([
  ["black", "#000000"],
  ["silver", "#c0c0c0"],
  ["gray", "#808080"],
  ["white", "#ffffff"],
  ["maroon", "#800000"],
  ["red", "#ff0000"],
  ["purple", "#800080"],
  ["fuchsia", "#ff00ff"],
  ["green", "#008000"],
  ["lime", "#00ff00"],
  ["olive", "#808000"],
  ["yellow", "#ffff00"],
  ["navy", "#000080"],
  ["blue", "#0000ff"],
  ["teal", "#008080"],
  ["aqua", "#00ffff"],
  ["orange", "#ffa500"],
  ["aliceblue", "#f0f8ff"],
  ["antiquewhite", "#faebd7"],
  ["aquamarine", "#7fffd4"],
  ["azure", "#f0ffff"],
  ["beige", "#f5f5dc"],
  ["bisque", "#ffe4c4"],
  ["blanchedalmond", "#ffebcd"],
  ["blueviolet", "#8a2be2"],
  ["brown", "#a52a2a"],
  ["burlywood", "#deb887"],
  ["cadetblue", "#5f9ea0"],
  ["chartreuse", "#7fff00"],
  ["chocolate", "#d2691e"],
  ["coral", "#ff7f50"],
  ["cornflowerblue", "#6495ed"],
  ["cornsilk", "#fff8dc"],
  ["crimson", "#dc143c"],
  ["cyan", "#00ffff"],
  ["darkblue", "#00008b"],
  ["darkcyan", "#008b8b"],
  ["darkgoldenrod", "#b8860b"],
  ["darkgray", "#a9a9a9"],
  ["darkgreen", "#006400"],
  ["darkgrey", "#a9a9a9"],
  ["darkkhaki", "#bdb76b"],
  ["darkmagenta", "#8b008b"],
  ["darkolivegreen", "#556b2f"],
  ["darkorange", "#ff8c00"],
  ["darkorchid", "#9932cc"],
  ["darkred", "#8b0000"],
  ["darksalmon", "#e9967a"],
  ["darkseagreen", "#8fbc8f"],
  ["darkslateblue", "#483d8b"],
  ["darkslategray", "#2f4f4f"],
  ["darkslategrey", "#2f4f4f"],
  ["darkturquoise", "#00ced1"],
  ["darkviolet", "#9400d3"],
  ["deeppink", "#ff1493"],
  ["deepskyblue", "#00bfff"],
  ["dimgray", "#696969"],
  ["dimgrey", "#696969"],
  ["dodgerblue", "#1e90ff"],
  ["firebrick", "#b22222"],
  ["floralwhite", "#fffaf0"],
  ["forestgreen", "#228b22"],
  ["gainsboro", "#dcdcdc"],
  ["ghostwhite", "#f8f8ff"],
  ["gold", "#ffd700"],
  ["goldenrod", "#daa520"],
  ["greenyellow", "#adff2f"],
  ["grey", "#808080"],
  ["honeydew", "#f0fff0"],
  ["hotpink", "#ff69b4"],
  ["indianred", "#cd5c5c"],
  ["indigo", "#4b0082"],
  ["ivory", "#fffff0"],
  ["khaki", "#f0e68c"],
  ["lavender", "#e6e6fa"],
  ["lavenderblush", "#fff0f5"],
  ["lawngreen", "#7cfc00"],
  ["lemonchiffon", "#fffacd"],
  ["lightblue", "#add8e6"],
  ["lightcoral", "#f08080"],
  ["lightcyan", "#e0ffff"],
  ["lightgoldenrodyellow", "#fafad2"],
  ["lightgray", "#d3d3d3"],
  ["lightgreen", "#90ee90"],
  ["lightgrey", "#d3d3d3"],
  ["lightpink", "#ffb6c1"],
  ["lightsalmon", "#ffa07a"],
  ["lightseagreen", "#20b2aa"],
  ["lightskyblue", "#87cefa"],
  ["lightslategray", "#778899"],
  ["lightslategrey", "#778899"],
  ["lightsteelblue", "#b0c4de"],
  ["lightyellow", "#ffffe0"],
  ["limegreen", "#32cd32"],
  ["linen", "#faf0e6"],
  ["magenta", "#ff00ff"],
  ["mediumaquamarine", "#66cdaa"],
  ["mediumblue", "#0000cd"],
  ["mediumorchid", "#ba55d3"],
  ["mediumpurple", "#9370db"],
  ["mediumseagreen", "#3cb371"],
  ["mediumslateblue", "#7b68ee"],
  ["mediumspringgreen", "#00fa9a"],
  ["mediumturquoise", "#48d1cc"],
  ["mediumvioletred", "#c71585"],
  ["midnightblue", "#191970"],
  ["mintcream", "#f5fffa"],
  ["mistyrose", "#ffe4e1"],
  ["moccasin", "#ffe4b5"],
  ["navajowhite", "#ffdead"],
  ["oldlace", "#fdf5e6"],
  ["olivedrab", "#6b8e23"],
  ["orangered", "#ff4500"],
  ["orchid", "#da70d6"],
  ["palegoldenrod", "#eee8aa"],
  ["palegreen", "#98fb98"],
  ["paleturquoise", "#afeeee"],
  ["palevioletred", "#db7093"],
  ["papayawhip", "#ffefd5"],
  ["peachpuff", "#ffdab9"],
  ["peru", "#cd853f"],
  ["pink", "#ffc0cb"],
  ["plum", "#dda0dd"],
  ["powderblue", "#b0e0e6"],
  ["rosybrown", "#bc8f8f"],
  ["royalblue", "#4169e1"],
  ["saddlebrown", "#8b4513"],
  ["salmon", "#fa8072"],
  ["sandybrown", "#f4a460"],
  ["seagreen", "#2e8b57"],
  ["seashell", "#fff5ee"],
  ["sienna", "#a0522d"],
  ["skyblue", "#87ceeb"],
  ["slateblue", "#6a5acd"],
  ["slategray", "#708090"],
  ["slategrey", "#708090"],
  ["snow", "#fffafa"],
  ["springgreen", "#00ff7f"],
  ["steelblue", "#4682b4"],
  ["tan", "#d2b48c"],
  ["thistle", "#d8bfd8"],
  ["tomato", "#ff6347"],
  ["turquoise", "#40e0d0"],
  ["violet", "#ee82ee"],
  ["wheat", "#f5deb3"],
  ["whitesmoke", "#f5f5f5"],
  ["yellowgreen", "#9acd32"],
  ["rebeccapurple", "#663399"],
]);

const HASH_PATTERN = new SafeRegExp(
  /^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/,
);
const SMALL_HASH_PATTERN = new SafeRegExp(
  /^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/,
);
const RGB_PATTERN = new SafeRegExp(
  /^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/,
);
const HSL_PATTERN = new SafeRegExp(
  /^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/,
);

function parseCssColor(colorString) {
  if (MapPrototypeHas(colorKeywords, colorString)) {
    colorString = MapPrototypeGet(colorKeywords, colorString);
  }
  // deno-fmt-ignore
  const hashMatch = StringPrototypeMatch(colorString, HASH_PATTERN);
  if (hashMatch != null) {
    return [
      Number(`0x${hashMatch[1]}`),
      Number(`0x${hashMatch[2]}`),
      Number(`0x${hashMatch[3]}`),
    ];
  }
  // deno-fmt-ignore
  const smallHashMatch = StringPrototypeMatch(colorString, SMALL_HASH_PATTERN);
  if (smallHashMatch != null) {
    return [
      Number(`0x${smallHashMatch[1]}0`),
      Number(`0x${smallHashMatch[2]}0`),
      Number(`0x${smallHashMatch[3]}0`),
    ];
  }
  // deno-fmt-ignore
  const rgbMatch = StringPrototypeMatch(colorString, RGB_PATTERN);
  if (rgbMatch != null) {
    return [
      MathRound(MathMax(0, MathMin(255, Number(rgbMatch[1])))),
      MathRound(MathMax(0, MathMin(255, Number(rgbMatch[2])))),
      MathRound(MathMax(0, MathMin(255, Number(rgbMatch[3])))),
    ];
  }
  // deno-fmt-ignore
  const hslMatch = StringPrototypeMatch(colorString, HSL_PATTERN);
  if (hslMatch != null) {
    // https://www.rapidtables.com/convert/color/hsl-to-rgb.html
    let h = Number(hslMatch[1]) % 360;
    if (h < 0) {
      h += 360;
    }
    const s = MathMax(0, MathMin(100, Number(hslMatch[2]))) / 100;
    const l = MathMax(0, MathMin(100, Number(hslMatch[3]))) / 100;
    const c = (1 - MathAbs(2 * l - 1)) * s;
    const x = c * (1 - MathAbs((h / 60) % 2 - 1));
    const m = l - c / 2;
    let r_;
    let g_;
    let b_;
    if (h < 60) {
      ({ 0: r_, 1: g_, 2: b_ } = [c, x, 0]);
    } else if (h < 120) {
      ({ 0: r_, 1: g_, 2: b_ } = [x, c, 0]);
    } else if (h < 180) {
      ({ 0: r_, 1: g_, 2: b_ } = [0, c, x]);
    } else if (h < 240) {
      ({ 0: r_, 1: g_, 2: b_ } = [0, x, c]);
    } else if (h < 300) {
      ({ 0: r_, 1: g_, 2: b_ } = [x, 0, c]);
    } else {
      ({ 0: r_, 1: g_, 2: b_ } = [c, 0, x]);
    }
    return [
      MathRound((r_ + m) * 255),
      MathRound((g_ + m) * 255),
      MathRound((b_ + m) * 255),
    ];
  }
  return null;
}

function getDefaultCss() {
  return {
    backgroundColor: null,
    color: null,
    fontWeight: null,
    fontStyle: null,
    textDecorationColor: null,
    textDecorationLine: [],
  };
}

const SPACE_PATTERN = new SafeRegExp(/\s+/g);

function parseCss(cssString) {
  const css = getDefaultCss();

  const rawEntries = [];
  let inValue = false;
  let currentKey = null;
  let parenthesesDepth = 0;
  let currentPart = "";
  for (let i = 0; i < cssString.length; i++) {
    const c = cssString[i];
    if (c == "(") {
      parenthesesDepth++;
    } else if (parenthesesDepth > 0) {
      if (c == ")") {
        parenthesesDepth--;
      }
    } else if (inValue) {
      if (c == ";") {
        const value = StringPrototypeTrim(currentPart);
        if (value != "") {
          ArrayPrototypePush(rawEntries, [currentKey, value]);
        }
        currentKey = null;
        currentPart = "";
        inValue = false;
        continue;
      }
    } else if (c == ":") {
      currentKey = StringPrototypeTrim(currentPart);
      currentPart = "";
      inValue = true;
      continue;
    }
    currentPart += c;
  }
  if (inValue && parenthesesDepth == 0) {
    const value = StringPrototypeTrim(currentPart);
    if (value != "") {
      ArrayPrototypePush(rawEntries, [currentKey, value]);
    }
    currentKey = null;
    currentPart = "";
  }

  for (let i = 0; i < rawEntries.length; ++i) {
    const { 0: key, 1: value } = rawEntries[i];
    if (key == "background-color") {
      if (value != null) {
        css.backgroundColor = value;
      }
    } else if (key == "color") {
      if (value != null) {
        css.color = value;
      }
    } else if (key == "font-weight") {
      if (value == "bold") {
        css.fontWeight = value;
      }
    } else if (key == "font-style") {
      if (
        ArrayPrototypeIncludes(["italic", "oblique", "oblique 14deg"], value)
      ) {
        css.fontStyle = "italic";
      }
    } else if (key == "text-decoration-line") {
      css.textDecorationLine = [];
      const lineTypes = StringPrototypeSplit(value, SPACE_PATTERN);
      for (let i = 0; i < lineTypes.length; ++i) {
        const lineType = lineTypes[i];
        if (
          ArrayPrototypeIncludes(
            ["line-through", "overline", "underline"],
            lineType,
          )
        ) {
          ArrayPrototypePush(css.textDecorationLine, lineType);
        }
      }
    } else if (key == "text-decoration-color") {
      const color = parseCssColor(value);
      if (color != null) {
        css.textDecorationColor = color;
      }
    } else if (key == "text-decoration") {
      css.textDecorationColor = null;
      css.textDecorationLine = [];
      const args = StringPrototypeSplit(value, SPACE_PATTERN);
      for (let i = 0; i < args.length; ++i) {
        const arg = args[i];
        const maybeColor = parseCssColor(arg);
        if (maybeColor != null) {
          css.textDecorationColor = maybeColor;
        } else if (
          ArrayPrototypeIncludes(
            ["line-through", "overline", "underline"],
            arg,
          )
        ) {
          ArrayPrototypePush(css.textDecorationLine, arg);
        }
      }
    }
  }

  return css;
}

function colorEquals(color1, color2) {
  return color1?.[0] == color2?.[0] && color1?.[1] == color2?.[1] &&
    color1?.[2] == color2?.[2];
}

function cssToAnsi(css, prevCss = null) {
  prevCss = prevCss ?? getDefaultCss();
  let ansi = "";
  if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) {
    if (css.backgroundColor == null) {
      ansi += "\x1b[49m";
    } else if (css.backgroundColor == "black") {
      ansi += `\x1b[40m`;
    } else if (css.backgroundColor == "red") {
      ansi += `\x1b[41m`;
    } else if (css.backgroundColor == "green") {
      ansi += `\x1b[42m`;
    } else if (css.backgroundColor == "yellow") {
      ansi += `\x1b[43m`;
    } else if (css.backgroundColor == "blue") {
      ansi += `\x1b[44m`;
    } else if (css.backgroundColor == "magenta") {
      ansi += `\x1b[45m`;
    } else if (css.backgroundColor == "cyan") {
      ansi += `\x1b[46m`;
    } else if (css.backgroundColor == "white") {
      ansi += `\x1b[47m`;
    } else {
      if (ArrayIsArray(css.backgroundColor)) {
        const { 0: r, 1: g, 2: b } = css.backgroundColor;
        ansi += `\x1b[48;2;${r};${g};${b}m`;
      } else {
        const parsed = parseCssColor(css.backgroundColor);
        if (parsed !== null) {
          const { 0: r, 1: g, 2: b } = parsed;
          ansi += `\x1b[48;2;${r};${g};${b}m`;
        } else {
          ansi += "\x1b[49m";
        }
      }
    }
  }
  if (!colorEquals(css.color, prevCss.color)) {
    if (css.color == null) {
      ansi += "\x1b[39m";
    } else if (css.color == "black") {
      ansi += `\x1b[30m`;
    } else if (css.color == "red") {
      ansi += `\x1b[31m`;
    } else if (css.color == "green") {
      ansi += `\x1b[32m`;
    } else if (css.color == "yellow") {
      ansi += `\x1b[33m`;
    } else if (css.color == "blue") {
      ansi += `\x1b[34m`;
    } else if (css.color == "magenta") {
      ansi += `\x1b[35m`;
    } else if (css.color == "cyan") {
      ansi += `\x1b[36m`;
    } else if (css.color == "white") {
      ansi += `\x1b[37m`;
    } else {
      if (ArrayIsArray(css.color)) {
        const { 0: r, 1: g, 2: b } = css.color;
        ansi += `\x1b[38;2;${r};${g};${b}m`;
      } else {
        const parsed = parseCssColor(css.color);
        if (parsed !== null) {
          const { 0: r, 1: g, 2: b } = parsed;
          ansi += `\x1b[38;2;${r};${g};${b}m`;
        } else {
          ansi += "\x1b[39m";
        }
      }
    }
  }
  if (css.fontWeight != prevCss.fontWeight) {
    if (css.fontWeight == "bold") {
      ansi += `\x1b[1m`;
    } else {
      ansi += "\x1b[22m";
    }
  }
  if (css.fontStyle != prevCss.fontStyle) {
    if (css.fontStyle == "italic") {
      ansi += `\x1b[3m`;
    } else {
      ansi += "\x1b[23m";
    }
  }
  if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) {
    if (css.textDecorationColor != null) {
      const { 0: r, 1: g, 2: b } = css.textDecorationColor;
      ansi += `\x1b[58;2;${r};${g};${b}m`;
    } else {
      ansi += "\x1b[59m";
    }
  }
  if (
    ArrayPrototypeIncludes(css.textDecorationLine, "line-through") !=
      ArrayPrototypeIncludes(prevCss.textDecorationLine, "line-through")
  ) {
    if (ArrayPrototypeIncludes(css.textDecorationLine, "line-through")) {
      ansi += "\x1b[9m";
    } else {
      ansi += "\x1b[29m";
    }
  }
  if (
    ArrayPrototypeIncludes(css.textDecorationLine, "overline") !=
      ArrayPrototypeIncludes(prevCss.textDecorationLine, "overline")
  ) {
    if (ArrayPrototypeIncludes(css.textDecorationLine, "overline")) {
      ansi += "\x1b[53m";
    } else {
      ansi += "\x1b[55m";
    }
  }
  if (
    ArrayPrototypeIncludes(css.textDecorationLine, "underline") !=
      ArrayPrototypeIncludes(prevCss.textDecorationLine, "underline")
  ) {
    if (ArrayPrototypeIncludes(css.textDecorationLine, "underline")) {
      ansi += "\x1b[4m";
    } else {
      ansi += "\x1b[24m";
    }
  }
  return ansi;
}

function inspectArgs(args, inspectOptions = {}) {
  circular = undefined;

  const noColor = colors.getNoColor();
  const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions };
  const first = args[0];
  let a = 0;
  let string = "";

  if (typeof first == "string" && args.length > 1) {
    a++;
    // Index of the first not-yet-appended character. Use this so we only
    // have to append to `string` when a substitution occurs / at the end.
    let appendedChars = 0;
    let usedStyle = false;
    let prevCss = null;
    for (let i = 0; i < first.length - 1; i++) {
      if (first[i] == "%") {
        const char = first[++i];
        if (a < args.length) {
          let formattedArg = null;
          if (char == "s") {
            // Format as a string.
            formattedArg = String(args[a++]);
          } else if (ArrayPrototypeIncludes(["d", "i"], char)) {
            // Format as an integer.
            const value = args[a++];
            if (typeof value == "bigint") {
              formattedArg = `${value}n`;
            } else if (typeof value == "number") {
              formattedArg = `${NumberParseInt(String(value))}`;
            } else {
              formattedArg = "NaN";
            }
          } else if (char == "f") {
            // Format as a floating point value.
            const value = args[a++];
            if (typeof value == "number") {
              formattedArg = `${value}`;
            } else {
              formattedArg = "NaN";
            }
          } else if (ArrayPrototypeIncludes(["O", "o"], char)) {
            // Format as an object.
            formattedArg = inspectValue(args[a++], rInspectOptions);
          } else if (char == "c") {
            const value = args[a++];
            if (!noColor) {
              const css = parseCss(value);
              formattedArg = cssToAnsi(css, prevCss);
              if (formattedArg != "") {
                usedStyle = true;
                prevCss = css;
              }
            } else {
              formattedArg = "";
            }
          }

          if (formattedArg != null) {
            string += StringPrototypeSlice(first, appendedChars, i - 1) +
              formattedArg;
            appendedChars = i + 1;
          }
        }
        if (char == "%") {
          string += StringPrototypeSlice(first, appendedChars, i - 1) + "%";
          appendedChars = i + 1;
        }
      }
    }
    string += StringPrototypeSlice(first, appendedChars);
    if (usedStyle) {
      string += "\x1b[0m";
    }
  }

  for (; a < args.length; a++) {
    if (a > 0) {
      string += " ";
    }
    if (typeof args[a] == "string") {
      string += args[a];
    } else {
      // Use default maximum depth for null or undefined arguments.
      string += inspectValue(args[a], rInspectOptions);
    }
  }

  if (rInspectOptions.indentLevel > 0) {
    const groupIndent = StringPrototypeRepeat(
      DEFAULT_INDENT,
      rInspectOptions.indentLevel,
    );
    string = groupIndent +
      StringPrototypeReplaceAll(string, "\n", `\n${groupIndent}`);
  }

  return string;
}

const countMap = new Map();
const timerMap = new Map();
const isConsoleInstance = Symbol("isConsoleInstance");

function getConsoleInspectOptions() {
  return {
    ...DEFAULT_INSPECT_OPTIONS,
    colors: !colors.getNoColor(),
  };
}

class Console {
  #printFunc = null;
  [isConsoleInstance] = false;

  constructor(printFunc) {
    this.#printFunc = printFunc;
    this.indentLevel = 0;
    this[isConsoleInstance] = true;

    // ref https://console.spec.whatwg.org/#console-namespace
    // For historical web-compatibility reasons, the namespace object for
    // console must have as its [[Prototype]] an empty object, created as if
    // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%.
    const console = ObjectCreate({}, {
      [SymbolToStringTag]: {
        enumerable: false,
        writable: false,
        configurable: true,
        value: "console",
      },
    });
    ObjectAssign(console, this);
    return console;
  }

  log = (...args) => {
    this.#printFunc(
      inspectArgs(args, {
        ...getConsoleInspectOptions(),
        indentLevel: this.indentLevel,
      }) + "\n",
      1,
    );
  };

  debug = (...args) => {
    this.#printFunc(
      inspectArgs(args, {
        ...getConsoleInspectOptions(),
        indentLevel: this.indentLevel,
      }) + "\n",
      0,
    );
  };

  info = (...args) => {
    this.#printFunc(
      inspectArgs(args, {
        ...getConsoleInspectOptions(),
        indentLevel: this.indentLevel,
      }) + "\n",
      1,
    );
  };

  dir = (obj = undefined, options = {}) => {
    this.#printFunc(
      inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) +
        "\n",
      1,
    );
  };

  dirxml = this.dir;

  warn = (...args) => {
    this.#printFunc(
      inspectArgs(args, {
        ...getConsoleInspectOptions(),
        indentLevel: this.indentLevel,
      }) + "\n",
      2,
    );
  };

  error = (...args) => {
    this.#printFunc(
      inspectArgs(args, {
        ...getConsoleInspectOptions(),
        indentLevel: this.indentLevel,
      }) + "\n",
      3,
    );
  };

  assert = (condition = false, ...args) => {
    if (condition) {
      return;
    }

    if (args.length === 0) {
      this.error("Assertion failed");
      return;
    }

    const [first, ...rest] = new SafeArrayIterator(args);

    if (typeof first === "string") {
      this.error(
        `Assertion failed: ${first}`,
        ...new SafeArrayIterator(rest),
      );
      return;
    }

    this.error(`Assertion failed:`, ...new SafeArrayIterator(args));
  };

  count = (label = "default") => {
    label = String(label);

    if (MapPrototypeHas(countMap, label)) {
      const current = MapPrototypeGet(countMap, label) || 0;
      MapPrototypeSet(countMap, label, current + 1);
    } else {
      MapPrototypeSet(countMap, label, 1);
    }

    this.info(`${label}: ${MapPrototypeGet(countMap, label)}`);
  };

  countReset = (label = "default") => {
    label = String(label);

    if (MapPrototypeHas(countMap, label)) {
      MapPrototypeSet(countMap, label, 0);
    } else {
      this.warn(`Count for '${label}' does not exist`);
    }
  };

  table = (data = undefined, properties = undefined) => {
    if (properties !== undefined && !ArrayIsArray(properties)) {
      throw new Error(
        "The 'properties' argument must be of type Array. " +
          "Received type string",
      );
    }

    if (data === null || typeof data !== "object") {
      return this.log(data);
    }

    const stringifyValue = (value) =>
      inspectValueWithQuotes(value, {
        ...DEFAULT_INSPECT_OPTIONS,
        depth: 1,
      });
    const toTable = (header, body) => this.log(cliTable(header, body));

    let resultData;
    const isSet = ObjectPrototypeIsPrototypeOf(SetPrototype, data);
    const isMap = ObjectPrototypeIsPrototypeOf(MapPrototype, data);
    const valuesKey = "Values";
    const indexKey = isSet || isMap ? "(iter idx)" : "(idx)";

    if (isSet) {
      resultData = [...new SafeSet(data)];
    } else if (isMap) {
      let idx = 0;
      resultData = {};

      MapPrototypeForEach(data, (v, k) => {
        resultData[idx] = { Key: k, Values: v };
        idx++;
      });
    } else {
      resultData = data;
    }

    const keys = ObjectKeys(resultData);
    const numRows = keys.length;

    const objectValues = properties
      ? ObjectFromEntries(
        ArrayPrototypeMap(
          properties,
          (name) => [name, ArrayPrototypeFill(new Array(numRows), "")],
        ),
      )
      : {};
    const indexKeys = [];
    const values = [];

    let hasPrimitives = false;
    keys.forEach((k, idx) => {
      const value = resultData[k];
      const primitive = value === null ||
        (typeof value !== "function" && typeof value !== "object");
      if (properties === undefined && primitive) {
        hasPrimitives = true;
        ArrayPrototypePush(values, stringifyValue(value));
      } else {
        const valueObj = value || {};
        const keys = properties || ObjectKeys(valueObj);
        for (let i = 0; i < keys.length; ++i) {
          const k = keys[i];
          if (!primitive && ReflectHas(valueObj, k)) {
            if (!(ReflectHas(objectValues, k))) {
              objectValues[k] = ArrayPrototypeFill(new Array(numRows), "");
            }
            objectValues[k][idx] = stringifyValue(valueObj[k]);
          }
        }
        ArrayPrototypePush(values, "");
      }

      ArrayPrototypePush(indexKeys, k);
    });

    const headerKeys = ObjectKeys(objectValues);
    const bodyValues = ObjectValues(objectValues);
    const headerProps = properties ||
      [
        ...new SafeArrayIterator(headerKeys),
        !isMap && hasPrimitives && valuesKey,
      ];
    const header = ArrayPrototypeFilter([
      indexKey,
      ...new SafeArrayIterator(headerProps),
    ], Boolean);
    const body = [indexKeys, ...new SafeArrayIterator(bodyValues), values];

    toTable(header, body);
  };

  time = (label = "default") => {
    label = String(label);

    if (MapPrototypeHas(timerMap, label)) {
      this.warn(`Timer '${label}' already exists`);
      return;
    }

    MapPrototypeSet(timerMap, label, DateNow());
  };

  timeLog = (label = "default", ...args) => {
    label = String(label);

    if (!MapPrototypeHas(timerMap, label)) {
      this.warn(`Timer '${label}' does not exists`);
      return;
    }

    const startTime = MapPrototypeGet(timerMap, label);
    const duration = DateNow() - startTime;

    this.info(`${label}: ${duration}ms`, ...new SafeArrayIterator(args));
  };

  timeEnd = (label = "default") => {
    label = String(label);

    if (!MapPrototypeHas(timerMap, label)) {
      this.warn(`Timer '${label}' does not exist`);
      return;
    }

    const startTime = MapPrototypeGet(timerMap, label);
    MapPrototypeDelete(timerMap, label);
    const duration = DateNow() - startTime;

    this.info(`${label}: ${duration}ms`);
  };

  group = (...label) => {
    if (label.length > 0) {
      this.log(...new SafeArrayIterator(label));
    }
    this.indentLevel += 2;
  };

  groupCollapsed = this.group;

  groupEnd = () => {
    if (this.indentLevel > 0) {
      this.indentLevel -= 2;
    }
  };

  clear = () => {
    this.indentLevel = 0;
    this.#printFunc(CSI.kClear, 1);
    this.#printFunc(CSI.kClearScreenDown, 1);
  };

  trace = (...args) => {
    const message = inspectArgs(
      args,
      { ...getConsoleInspectOptions(), indentLevel: 0 },
    );
    const err = {
      name: "Trace",
      message,
    };
    ErrorCaptureStackTrace(err, this.trace);
    this.error(err.stack);
  };

  // These methods are noops, but when the inspector is connected, they
  // call into V8.
  profile = (_label) => {};
  profileEnd = (_label) => {};
  timeStamp = (_label) => {};

  static [SymbolHasInstance](instance) {
    return instance[isConsoleInstance];
  }
}

const customInspect = SymbolFor("Deno.customInspect");

function inspect(
  value,
  inspectOptions = {},
) {
  circular = undefined;
  return inspectValue(value, {
    ...DEFAULT_INSPECT_OPTIONS,
    ...inspectOptions,
  });
}

/** Creates a proxy that represents a subset of the properties
 * of the original object optionally without evaluating the properties
 * in order to get the values. */
function createFilteredInspectProxy({ object, keys, evaluate }) {
  return new Proxy({}, {
    get(_target, key) {
      if (key === SymbolToStringTag) {
        return object.constructor?.name;
      } else if (ArrayPrototypeIncludes(keys, key)) {
        return ReflectGet(object, key);
      } else {
        return undefined;
      }
    },
    getOwnPropertyDescriptor(_target, key) {
      if (!ArrayPrototypeIncludes(keys, key)) {
        return undefined;
      } else if (evaluate) {
        return getEvaluatedDescriptor(object, key);
      } else {
        return getDescendantPropertyDescriptor(object, key) ??
          getEvaluatedDescriptor(object, key);
      }
    },
    has(_target, key) {
      return ArrayPrototypeIncludes(keys, key);
    },
    ownKeys() {
      return keys;
    },
  });

  function getDescendantPropertyDescriptor(object, key) {
    let propertyDescriptor = ReflectGetOwnPropertyDescriptor(object, key);
    if (!propertyDescriptor) {
      const prototype = ReflectGetPrototypeOf(object);
      if (prototype) {
        propertyDescriptor = getDescendantPropertyDescriptor(prototype, key);
      }
    }
    return propertyDescriptor;
  }

  function getEvaluatedDescriptor(object, key) {
    return {
      configurable: true,
      enumerable: true,
      value: object[key],
    };
  }
}

// A helper function that will bind our own console implementation
// with default implementation of Console from V8. This will cause
// console messages to be piped to inspector console.
//
// We are using `Deno.core.callConsole` binding to preserve proper stack
// frames in inspector console. This has to be done because V8 considers
// the last JS stack frame as gospel for the inspector. In our case we
// specifically want the latest user stack frame to be the one that matters
// though.
//
// Inspired by:
// https://github.com/nodejs/node/blob/1317252dfe8824fd9cfee125d2aaa94004db2f3b/lib/internal/util/inspector.js#L39-L61
function wrapConsole(consoleFromDeno, consoleFromV8) {
  const callConsole = core.callConsole;

  const keys = ObjectKeys(consoleFromV8);
  for (let i = 0; i < keys.length; ++i) {
    const key = keys[i];
    if (ObjectPrototypeHasOwnProperty(consoleFromDeno, key)) {
      consoleFromDeno[key] = FunctionPrototypeBind(
        callConsole,
        consoleFromDeno,
        consoleFromV8[key],
        consoleFromDeno[key],
      );
    } else {
      // Add additional console APIs from the inspector
      consoleFromDeno[key] = consoleFromV8[key];
    }
  }
}

// Expose these fields to internalObject for tests.
internals.Console = Console;
internals.cssToAnsi = cssToAnsi;
internals.inspectArgs = inspectArgs;
internals.parseCss = parseCss;
internals.parseCssColor = parseCssColor;

export {
  Console,
  createFilteredInspectProxy,
  CSI,
  customInspect,
  inspect,
  inspectArgs,
  quoteString,
  wrapConsole,
};
